package com.zc.utils;

import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.msg.*;

/**
 * @description:
 * @author: cong
 * @create: 2025/1/4
 */
public class Modbus4jWriteUtils {
    static ModbusFactory modbusFactory = new ModbusFactory();

    /**
     * 获取Modbus主站实例
     *
     * 该方法用于初始化并返回一个Modbus主站对象，该对象用于与从站设备进行通信
     * 方法内部设置了主站的IP参数，包括主机地址和端口号，然后通过Modbus工厂创建主站实例
     *
     * @return ModbusMaster 初始化的Modbus主站实例
     * @throws Exception 如果主站初始化失败，抛出异常
     */
    public static ModbusMaster getMaster() throws Exception {
        // 创建IP参数对象，用于设置主站与从站通信的网络参数
        IpParameters ipParameters = new IpParameters();
        // 设置从站的主机地址
        ipParameters.setHost("192.168.88.33");
        // 设置从站的通信端口
        ipParameters.setPort(502);

        // 通过Modbus工厂创建TCP主站实例，并设置为保持连接
        ModbusMaster master = modbusFactory.createTcpMaster(ipParameters, true);

        // 初始化主站，准备与从站建立通信
        master.init();

        // 返回初始化完成的主站实例
        return master;
    }

    /**
     * 写入线圈状态
     *
     * 此方法用于向指定从设备的线圈写入一个布尔状态
     * 它通过调用Modbus主站的send方法来发送一个WriteCoilRequest请求，并接收一个WriteCoilResponse响应
     *
     * @param slaveId 从设备ID，标识与之通信的从设备
     * @param offset 线圈偏移量，指定要写入的线圈位置
     * @param status 要写入线圈的状态，true代表闭合，false代表断开
     * @return 返回写入操作是否成功，如果成功，返回true；如果发生异常，抛出Exception
     * @throws Exception 如果写入操作失败，抛出异常
     */
    public static boolean writeCoilStatus(int slaveId, int offset, boolean status) throws Exception {
        // 获取Modbus主站实例
        ModbusMaster master = getMaster();

        // 创建一个写入线圈请求，指定从设备ID、线圈偏移量和要写入的状态
        WriteCoilRequest writeCoilRequest = new WriteCoilRequest(slaveId, offset, status);

        // 发送写入线圈请求并接收响应
        WriteCoilResponse response = (WriteCoilResponse)master.send(writeCoilRequest);

        // 判断响应是否为异常响应，如果不是异常响应，则返回true，表示写入操作成功
        return !response.isException();
    }

    /**
     * 写入多个线圈的状态
     *
     * 此方法用于向Modbus从设备写入一组线圈的状态每个线圈的状态由一个布尔值表示，
     * 其中true表示线圈闭合，false表示线圈断开
     *
     * @param slaveId 从设备的地址，标识与之通信的特定从设备
     * @param offset 线圈的起始地址，表示写入操作开始的位置
     * @param status 线圈的状态数组，包含要写入的每个线圈的布尔状态
     * @return 返回操作是否成功如果写入操作没有引起异常，则返回true
     * @throws Exception 如果写入操作失败或遇到其他错误，则抛出异常
     */
    public static boolean writeCoils(int slaveId, int offset, boolean[] status) throws Exception {
        // 获取Modbus主设备实例
        ModbusMaster master = getMaster();

        // 创建写入多个线圈状态的请求
        WriteCoilsRequest writeCoilsRequest = new WriteCoilsRequest(slaveId, offset, status);

        // 发送写入请求并接收响应
        WriteCoilsResponse response = (WriteCoilsResponse) master.send(writeCoilsRequest);

        // 检查响应是否为异常响应
        return !response.isException();
    }

    /**
     * 向指定从机写入寄存器值
     *
     * @param slaveId 从机ID，用于标识与之通信的从机设备
     * @param writeOffset 寄存器偏移地址，表示要写入的寄存器位置
     * @param writeValue 要写入的值，短整型数据
     * @return 写入操作是否成功，如果成功则返回true，否则返回false
     * @throws Exception 如果在通信过程中发生错误，则抛出异常
     */
    public static boolean writeRegister(int slaveId, int writeOffset, short writeValue) throws Exception {
        // 获取Modbus主站实例
        ModbusMaster master = getMaster();

        // 创建写寄存器请求对象，包含从机ID、寄存器偏移地址和要写入的值
        WriteRegisterRequest writeRegisterRequest = new WriteRegisterRequest(slaveId, writeOffset, writeValue);

        // 发送写寄存器请求，并接收响应对象
        WriteRegisterResponse response = (WriteRegisterResponse) master.send(writeRegisterRequest);

        // 判断响应是否为异常响应，如果不是异常响应则表示写入操作成功
        return !response.isException();
    }

    /**
     * 向指定从设备写入一系列寄存器值
     *
     * @param slaveId 从设备ID，标识与之通信的从设备
     * @param writeOffset 寄存器起始地址，表示写入的起始位置
     * @param writeValues 要写入的寄存器值数组，包含待写入的数据
     * @return 如果写入操作成功且没有发生异常，则返回true；否则返回false
     * @throws Exception 如果写入操作失败，抛出异常
     */
    public static boolean writeRegisters(int slaveId, int writeOffset, short[] writeValues) throws Exception {
        // 获取Modbus主设备实例
        ModbusMaster master = getMaster();

        // 创建写入寄存器请求对象
        WriteRegistersRequest writeRegistersRequest = new WriteRegistersRequest(slaveId, writeOffset, writeValues);

        // 发送写入寄存器请求并接收响应
        WriteRegistersResponse response = (WriteRegistersResponse) master.send(writeRegistersRequest);

        // 判断响应是否为异常响应
        return !response.isException();
    }

    /**
     * 写入保持寄存器的值
     *
     * 此方法通过Modbus协议向指定从设备的保持寄存器写入一个数值
     * 选择保持寄存器是由slaveId和offset确定的设备地址和寄存器偏移量
     *
     * @param slaveId 从设备的地址，标识与之通信的目标设备
     * @param offset 寄存器的偏移量，表示要写入的寄存器位置
     * @param value 要写入的数值，类型为Number，可以是各种数值类型
     * @param dataType 数据类型，指定要写入数据的类型，以确保正确写入
     * @throws Exception 如果写入操作失败，则抛出异常
     */
    public static void writeHoldingRegister(int slaveId, int offset, Number value, int dataType) throws Exception {
        // 获取Modbus主设备实例
        ModbusMaster master = getMaster();

        // 创建一个定位器，指定要访问的保持寄存器的位置和数据类型
        BaseLocator<Number> baseLocator = BaseLocator.holdingRegister(slaveId, offset, dataType);

        // 通过Modbus主设备向指定的保持寄存器写入数值
        master.setValue(baseLocator, value);
    }
}
